using System.Net;
using System.Net.Sockets;
using System.Runtime.InteropServices;

namespace Seer.SeerSocket;
public struct SeerMessageHead
{
    public byte sync;  // 0x5A
    public byte version; // 0x01
    public ushort number; // 0-65536
    public uint length; // 数据区长度,json序列化数据长度
    public ushort type; // 报文的类型

#pragma warning disable 0414 //禁用警告
#pragma warning disable 0169 //禁用警告
    private readonly byte ref0;      //保留
    private readonly byte ref1;      //保留
    private readonly byte ref2;      //保留
    private readonly byte ref3;      //保留
    private readonly byte ref4;      //保留
    private readonly byte ref5;      //保留
#pragma warning restore 0414 //恢复警告
#pragma warning restore 0169 //恢复警告


    public SeerMessageHead()
    {
        this.sync = 0x5A;
        this.version = 0x01;
        this.number = 0x0001;
    }
    public SeerMessageHead(ushort type)
    {
        this.type = type;
    }





    public readonly byte[] ToBytes()
    {

        int size = Marshal.SizeOf(this);
        byte[] bytes = new byte[size];

        IntPtr ptr = Marshal.AllocHGlobal(size);
        Marshal.StructureToPtr(this, ptr, true);
        Marshal.Copy(ptr, bytes, 0, size);
        Marshal.FreeHGlobal(ptr);

        if (BitConverter.IsLittleEndian)
        {
            Array.Reverse(bytes, 2, 2);
            Array.Reverse(bytes, 4, 4);
            Array.Reverse(bytes, 8, 2);
        }
        return bytes;

    }


};



public class SeerSocket
{
    public string ipAddress;
    private readonly Dictionary<int, TcpClient> clients;

    public SeerSocket(string ipAddress, bool onlyRead = true)
    {
        this.ipAddress = ipAddress;
        try
        {
            this.clients = new Dictionary<int, TcpClient>
        {
            { 19204, new TcpClient(this.ipAddress, 19204) }
        };
            if (onlyRead) return;
            this.clients.Add(19205, new TcpClient(this.ipAddress, 19205));
            this.clients.Add(19206, new TcpClient(this.ipAddress, 19206));
            this.clients.Add(19207, new TcpClient(this.ipAddress, 19207));
            this.clients.Add(19210, new TcpClient(this.ipAddress, 19210));
        }catch{
            System.Console.WriteLine("connect fail");
        }

    }

    // 使用键来访问特定的TcpClient对象
    private TcpClient GetClient(int port)
    {
        return clients[port];
    }


    public string Send(ushort type, string msg = "", int port = 19204)
    {
        var msgBody = NormalStrToHexByte(msg);
        var msgHead = CreateMsgHead(type, (uint)msgBody.Length);
        var response = this.SendRaw(msgHead, msgBody, port);
        return response;
    }

    private string SendRaw(byte[] msgHead, byte[] msgBody, int port)
    {
        try
        {
            TcpClient client = this.GetClient(port);
            if (!client.Connected)
            {
                this.clients[port] = new TcpClient(ipAddress, port);
                Console.WriteLine($"{ipAddress}:{port}:断开连接");
                return "fail";
            }

            NetworkStream serverStream = client.GetStream();
            serverStream.Write(msgHead, 0, msgHead.Length);
            serverStream.Write(msgBody, 0, msgBody.Length);
            serverStream.Flush();

            byte[] inStream = new byte[16];
            while (16 != serverStream.Read(inStream, 0, 16))
            {
                Thread.Sleep(20);
            }
            var recv_head = BytesToStructure<SeerMessageHead>(inStream);
            byte[] recvbyte = BitConverter.GetBytes(recv_head.length);
            Array.Reverse(recvbyte);
            // 获取数据区的长度
            var dsize = BitConverter.ToUInt32(recvbyte, 0);
            const int bufferSize = 512;
            List<byte> datalist = new List<byte>();
            int count = 0;
            while (true)
            {
                byte[] buffer = new byte[bufferSize];
                int readSize = serverStream.Read(buffer, 0, bufferSize);
                count += readSize;
                datalist.AddRange(buffer);
                if (count == dsize)
                {
                    break;
                }
            }
            // var content = BitConverter.ToString(SeerMessageHeadToBytes(recv_head)).Replace("-", " ");
            // Console.WriteLine(content);
            string resMsg = System.Text.Encoding.UTF8.GetString(datalist.ToArray());
            return resMsg;
        }
        catch (SocketException)
        {
            return "fail";
        }
        catch (IOException)
        {
            return "fail";
        }
    }

    public void CloseAll()
    {
        foreach (var client in this.clients.Values)
        {
            client.Close();
        }
    }

    private T BytesToStructure<T>(byte[] bytesBuffer)
    {
        if (bytesBuffer.Length < Marshal.SizeOf(typeof(T)))
        {
            throw new ArgumentException("size error");
        }

        nint bufferHandler = Marshal.AllocHGlobal(bytesBuffer.Length);

        for (int index = 0; index < bytesBuffer.Length; index++)
        {
            Marshal.WriteByte(bufferHandler, index, bytesBuffer[index]);
        }

        // T? structObject = (T)Marshal.PtrToStructure(bufferHandler, typeof(T));
        T? structObject = Marshal.PtrToStructure<T>(bufferHandler);

        Marshal.FreeHGlobal(bufferHandler);

        if (structObject == null)
        {
            throw new InvalidOperationException("Failed to convert bytes to structure.");
        }

        return structObject!;
    }

    private byte[] NormalStrToHexByte(string str)
    {
        byte[] result = new byte[str.Length];

        byte[] buffer = System.Text.Encoding.UTF8.GetBytes(str);

        for (int i = 0; i < buffer.Length; i++)
        {
            result[i] = Convert.ToByte(buffer[i].ToString("X2"), 16);
        }
        return result;
    }

    private byte[] CreateMsgHead(ushort type, uint msgLen)
    {
        var msgHead = new SeerMessageHead
        {
            type = type,
            length = msgLen
        };
        return msgHead.ToBytes();
    }

    public static void PrintBytes(byte[] bytes)
    {
        foreach (byte b in bytes)
        {
            Console.Write("{0:X2} ", b);
        }

    }





}


